package com.hdu.kdk_es_orm.service;


import com.hdu.kdk_es_orm.annotation.DocumentId;
import com.hdu.kdk_es_orm.annotation.Index;
import com.hdu.kdk_es_orm.annotation.LogicDelete;
import com.hdu.kdk_es_orm.core.QueryFilter;
import com.hdu.kdk_es_orm.wrapper.EsWrapper;
import lombok.val;
import org.elasticsearch.index.IndexNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;


import static com.hdu.kdk_es_orm.core.FilterType.IN;
import static com.hdu.kdk_es_orm.utils.ReflectionUtils.*;
import static com.hdu.kdk_es_orm.wrapper.EsWrapper.of;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static org.apache.commons.collections4.CollectionUtils.isEmpty;

/**
 * 提供基础 crud 能力
 *
 * @author qiuyuanhai
 */
public class BaseService<T> {

    final Logger logger = LoggerFactory.getLogger(BaseService.class);
    private final Class<T> entityClass;
    private static final String DEFAULT_ID_FIELD_NAME = "id";


    @SuppressWarnings("unchecked")
    public BaseService() {
        val genericSuperclass = this.getClass().getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType) {
            val pt = (ParameterizedType) genericSuperclass;
            val actualTypeArgument = pt.getActualTypeArguments()[0];
            this.entityClass = (Class<T>) actualTypeArgument;
        } else {
            throw new RuntimeException("没有获取到合适的泛型信息");
        }
    }

    public boolean deleteById(Object docId) {
        if (docId == null) {
            logger.warn("deleteById: docId cannot be null");
            return false;
        }
        try {
            return EsService.delete(
                    docId.toString(),
                    getIndexName()
            );
        } catch (IllegalArgumentException | IllegalStateException e) {
            logger.warn("deleteById exception , id : {}", docId, e);
            return false;
        } catch (Exception e) {
            logger.warn("deleteById: An unexpected error occurred, id : {}", docId, e);
            return false;
        }
    }


    public boolean update(T obj) {
        try {
            return EsService.update(
                    obj,
                    getIdValue(obj),
                    getIndexName()
            );
        } catch (IndexNotFoundException e) {
            logger.error("update ", e);
            return false;
        } catch (Exception e) {
            logger.error("未知错误导致更新失败", e);
            return false;
        }
    }

    public boolean saveOrUpdate(T obj) {
        try {
            // 假设EsService.saveOrUpdate方法签名没有改变
            return EsService.saveOrUpdate(
                    obj,
                    getIdValue(obj),
                    getIndexName()
            );
        } catch (IllegalArgumentException e) {
            logger.error("非法参数异常, obj : {}", obj.toString(), e);
            return false;
        } catch (Exception e) {
            // 捕获其他未指定的异常
            logger.error("未知异常, obj : {}", obj, e);
            return false;
        }
    }

    public boolean save(T obj) {
        try {
            return this.batchSave(singletonList(obj));
        } catch (Exception e) {
            return false;
        }
    }

    public boolean batchSave(List<T> documents) {
        if (isEmpty(documents)) {
            return false;
        }
        documents.forEach(this::setLogicExistValue);
        try {
            return EsService.batchSave(
                    documents,
                    getIdValues(documents),
                    getIndexName()
            );
        } catch (Exception e) {
            return false;
        }
    }

    public T getOne(EsWrapper<T> wrapper) {
        List<T> list = list(wrapper);
        return isEmpty(list) ? null : list.get(0);
    }

    public List<T> list(EsWrapper<T> wrapper) {
        // 索引名称
        String indexName = getIndexName();
        return EsService.queryList(
                wrapper,
                indexName
        );
    }

    public Long count(EsWrapper<T> wrapper) {
        return EsService.count(wrapper, getIndexName());
    }

    public T getById(Object id) {
        List<T> res = this.listByIds(singletonList(id.toString()));
        return isEmpty(res) ? null : res.get(0);
    }

    public List<T> listByIds(Collection<? extends Serializable> idList) {

        val idFieldName = ofNullable(getAnnotationOnFields(entityClass, DocumentId.class))
                .map(DocumentId::documentId)
                .orElse(DEFAULT_ID_FIELD_NAME);

        val queryFilter = new QueryFilter();
        queryFilter.setType(IN);
        queryFilter.setIn(Arrays.asList(idList.toArray()));
        queryFilter.setField(idFieldName);

        val indexName = getIndexName();

        int size = idList.size();
        int from = 0;

        val esWrapper = of(entityClass);
        esWrapper.setFrom(from);
        esWrapper.setSize(size);
        esWrapper.setQueryFilter(queryFilter);

        return EsService.queryList(
                esWrapper,
                indexName
        );
    }


    private String getIndexName() {
        return ofNullable(getAnnotationOnClass(entityClass, Index.class))
                .map(Index::indexName)
                .orElse(entityClass.getSimpleName().toLowerCase());
    }


    private String getIdValue(T obj) {
        try {
            return getFieldValueByTargetAnnotation(DocumentId.class, obj).toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Integer getLogicExistValue() {
        LogicDelete logicDelete = getAnnotationOnFields(this.entityClass, LogicDelete.class);
        return logicDelete.existValue();
    }

    private void setLogicExistValue(Object obj) {
        Integer existValue = getLogicExistValue();
        setFiledValueByTargetAnnotation(
                LogicDelete.class,
                obj,
                existValue
        );
    }

    private List<String> getIdValues(List<T> objs) {
        return objs
                .stream()
                .map(this::getIdValue)
                .collect(Collectors.toList());
    }

}
